`timescale 1ns / 1ps
/******************************************************************************
*                                                                             *
* UTICA softcore v0.1                                                         *
*                                                                             *
* Copyright (c) 2012 Andrew D. Zonenberg                                      *
* All rights reserved.                                                        *
*                                                                             *
* Redistribution and use in source and binary forms, with or without modifi-  *
* cation, are permitted provided that the following conditions are met:       *
*                                                                             *
*    * Redistributions of source code must retain the above copyright notice  *
*      this list of conditions and the following disclaimer.                  *
*                                                                             *
*    * Redistributions in binary form must reproduce the above copyright      *
*      notice, this list of conditions and the following disclaimer in the    *
*      documentation and/or other materials provided with the distribution.   *
*                                                                             *
*    * Neither the name of the author nor the names of any contributors may be*
*      used to endorse or promote products derived from this software without *
*      specific prior written permission.                                     *
*                                                                             *
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED *
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF        *
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN     *
* NO EVENT SHALL THE AUTHORS BE HELD LIABLE FOR ANY DIRECT, INDIRECT,         *
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT    *
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,   *
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY       *
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT         *
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF    *
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.           *
*                                                                             *
*****************************************************************************/

/**
	@file twosetcache.v
	@author griffin.milsap@gmail.com
	@brief A single 8k two way set associative cache block made from two 4k
		direct mapped blocks.
**/

module twosetcache(
	clk,
	up_rd, up_wr, up_mask, up_addr, up_din, up_dout, up_doutok, // Upstream (toward CPU) bus
	dn_rd, dn_wr, dn_mask, dn_addr, dn_din, dn_dout, dn_dinok   // Downstream (away from CPU) bus
	);
	
	// State definitions
	localparam INIT = 0;
	localparam ISSUE_FIND = 1;
	localparam FIND = 2;
	localparam PRE_OP = 3;
	localparam OPERATE = 4;
	
	// IO Definitions
	input wire clk;
	
	// Upstream bus
	input wire up_rd;
	input wire up_wr;
	input wire[3:0] up_mask;
	input wire[31:0] up_addr;
	input wire[31:0] up_din;
	output reg[31:0] up_dout = 0;
	output reg up_doutok = 0;

	// Downstream bus
	output reg dn_rd = 0;
	output reg dn_wr = 0;
	output reg[3:0] dn_mask = 0;
	output reg[31:0] dn_addr = 0;
	input wire[31:0] dn_din;
	output reg[31:0] dn_dout = 0;
	input wire dn_dinok;
	
	// Synchronous Buffers
	reg up_rd_buf = 0;
	reg up_wr_buf = 0;
	reg[31:0] up_addr_buf = 0;
	reg[31:0] up_din_buf = 0;
	reg[3:0] up_mask_buf = 0;
	reg uncacheable_buf = 0;
	
	// Uncacheable Forwarding
	wire uncacheable; // If set, don't cache this
	assign uncacheable = (up_addr[31:29] == 3'b101);
	
	// Cache connection
	reg connect_cache = 0;
	reg cache_set = 0;
	reg round_robin = 0;
	
	// Query registers
	reg noncache = 0;
	assign set0_noncache = noncache;
	assign set1_noncache = noncache;
	
	// State Machine
	reg[15:0] state = INIT;
	always @(posedge clk) begin
	
		case(state)
			
			// INIT STATE
			INIT: begin
				$display("[twosetcache] State: INIT -- up_rd = 0b%b, up_wr = 0b%b", up_rd, up_wr);
			
				// Buffer input necessary for completing potential cached operation
				up_rd_buf <= up_rd;
				up_wr_buf <= up_wr;
				up_mask_buf <= up_mask;
				up_addr_buf <= up_addr;
				up_din_buf <= up_din;
				uncacheable_buf <= uncacheable;
				
				// Ensure that no cache is connected
				connect_cache <= 0;
				
				// Move to the find state if we have a cached operation
				if((up_rd | up_wr) && !uncacheable) state <= ISSUE_FIND;
				
			end
			
			// ISSUE_FIND STATE
			ISSUE_FIND: begin
				$display("[twosetcache] State: ISSUE_FIND");
				state <= FIND;
			end
			
			// FIND STATE
			FIND: begin
				$display("[twosetcache] State: FIND");
				
				// Once the query has concluded for both sets, determine which (if any) hit
				if(set0_query_done && set1_query_done) begin
					
					// Select a cache to connect
					if(set0_up_doutok) cache_set <= 0;
					else if(set1_up_doutok) cache_set <= 1;
					else begin // Cache miss -- connect whichever set
						$display("Cache miss!  Picking round_robin set %b", round_robin);
						cache_set <= round_robin;
						round_robin <= ~round_robin; // DANGER, DANGER WILL ROBINSON! THIS MAY NOT WORK
					end
					
					// Connect the chosen cache and set the state to operation
					connect_cache <= 1;
					state <= PRE_OP;
				end
				
			end
			
			// Wait for the result from the find test to clear out before connecting the cache
			PRE_OP: begin
				$display("[twosetcache] State: PRE_OP");
				state <= OPERATE;
			end
			
			// OPERATE STATE: Issue the operate
			OPERATE: begin
				$display("[twosetcache] State: OPERATE: up_rd_buf: 0b%b, up_wr_buf 0b%b", up_rd_buf, up_wr_buf);
				$display("[twosetcache] connect_cache = 0b%b, cache_set = 0b%b", connect_cache, cache_set);
			
				// Wait for the cache to perform its operation, then go back to init.
				if(up_rd_buf && up_doutok) state <= INIT;
				if(up_wr_buf && dn_wr) state <= INIT;
			end
		
		endcase
	end
	
	// Assign wires in order to trigger on state changes
	wire init_state, find_state, preop_state, operate_state;
	assign init_state = (state == INIT);
	assign issue_find_state = (state == ISSUE_FIND);
	assign find_state = (state == FIND);
	assign preop_state = (state == PRE_OP);
	assign operate_state = (state == OPERATE);
	
	// Connect the correct outputs
	always @(posedge clk, uncacheable,
		init_state, issue_find_state, find_state, preop_state, operate_state, uncacheable_buf,
		up_rd, up_wr, up_mask, up_din, up_addr, dn_din, dn_dinok) begin
		
		// Disconnect Outputs
		dn_addr <= 0;
		dn_rd <= 0;
		dn_wr <= 0;
		dn_dout <= 0;
		dn_mask <= 0;
		up_dout <= 0;
		up_doutok <= 0;
		
		// Disconnect Cache Set 0
		set0_up_rd <= 0;
		set0_up_wr <= 0;
		set0_up_mask <= 0;
		set0_up_addr <= 0;
		set0_up_din <= 0;
		set0_dn_din <= 0;
	
		// Disconnect Cache Set 1
		set1_up_rd <= 0;
		set1_up_wr <= 0;
		set1_up_mask <= 0;
		set1_up_addr <= 0;
		set1_up_din <= 0;
		set1_dn_din <= 0;
		
		noncache <= 0;
	
		// Combinatorial Forward in the case of an uncacheable address
		if(uncacheable | uncacheable_buf) begin
			$display( "Forwarding" );
			dn_addr <= up_addr;
			dn_rd <= up_rd;
			dn_wr <= up_wr;
			dn_dout <= up_din;
			dn_mask <= up_mask;
			up_dout <= dn_din;
			up_doutok <= dn_dinok;
		end
		
		// Issue the noncache read while we're in the FIND state
		if(issue_find_state) begin
			noncache <= 1;
			set0_up_rd <= 1;
			set1_up_rd <= 1;
			set0_up_addr <= up_addr_buf;
			set1_up_addr <= up_addr_buf;
		end
		
		// Connect a cache set to the inputs and outputs
		if(operate_state) begin
			
			if(cache_set == 0) begin
				$display("Cache set 0 connected!");
				set0_up_rd <= up_rd_buf;
				set0_up_wr <= up_wr_buf;
				set0_up_mask <= up_mask_buf;
				set0_up_addr <= up_addr_buf;
				set0_up_din <= up_din_buf;
				up_dout <= set0_up_dout;
				up_doutok <= set0_up_doutok;
				dn_rd <= set0_dn_rd;
				dn_wr <= set0_dn_wr;
				dn_mask <= set0_dn_mask;
				dn_addr <= set0_dn_addr;
				set0_dn_din <= dn_din;
				dn_dout <= set0_dn_dout;
				set0_dn_dinok <= dn_dinok;	
			end
			else begin
				$display("Cache set 1 connected!");
				set1_up_rd <= up_rd_buf;
				set1_up_wr <= up_wr_buf;
				set1_up_mask <= up_mask_buf;
				set1_up_addr <= up_addr_buf;
				set1_up_din <= up_din_buf;
				up_dout <= set1_up_dout;
				up_doutok <= set1_up_doutok;
				dn_rd <= set1_dn_rd;
				dn_wr <= set1_dn_wr;
				dn_mask <= set1_dn_mask;
				dn_addr <= set1_dn_addr;
				set1_dn_din <= dn_din;
				dn_dout <= set1_dn_dout;
				set1_dn_dinok <= dn_dinok;
			end
		end
		
	end
	
	// Cache set 0
	wire set0_query_done;
	reg set0_up_rd;
	reg set0_up_wr;
	reg[3:0] set0_up_mask;
	reg[31:0] set0_up_addr;
	reg[31:0] set0_up_din;
	wire[31:0] set0_up_dout;
	wire set0_up_doutok;
	wire set0_dn_rd;
	wire set0_dn_wr;
	wire[3:0] set0_dn_mask;
	wire[31:0] set0_dn_addr;
	reg[31:0] set0_dn_din;
	wire[31:0] set0_dn_dout;
	reg set0_dn_dinok;
	cache set0 (
		.clk(clk),
		.noncache(noncache),
		.query_done(set0_query_done),
		.up_rd(set0_up_rd),
		.up_wr(set0_up_wr),
		.up_mask(set0_up_mask),
		.up_addr(set0_up_addr),
		.up_din(set0_up_din),
		.up_dout(set0_up_dout),
		.up_doutok(set0_up_doutok),
		.dn_rd(set0_dn_rd),
		.dn_wr(set0_dn_wr),
		.dn_mask(set0_dn_mask),
		.dn_addr(set0_dn_addr),
		.dn_din(set0_dn_din),
		.dn_dout(set0_dn_dout),
		.dn_dinok(set0_dn_dinok)
		);
		
	// Cache set 1
	wire set1_query_done;
	reg set1_up_rd;
	reg set1_up_wr;
	reg[3:0] set1_up_mask;
	reg[31:0] set1_up_addr;
	reg[31:0] set1_up_din;
	wire[31:0] set1_up_dout;
	wire set1_up_doutok;
	wire set1_dn_rd;
	wire set1_dn_wr;
	wire[3:0] set1_dn_mask;
	wire[31:0] set1_dn_addr;
	reg[31:0] set1_dn_din;
	wire[31:0] set1_dn_dout;
	reg set1_dn_dinok;
	cache set1 (
		.clk(clk),
		.noncache(noncache),
		.query_done(set1_query_done),
		.up_rd(set1_up_rd),
		.up_wr(set1_up_wr),
		.up_mask(set1_up_mask),
		.up_addr(up_addr_buf),
		.up_din(set1_up_din),
		.up_dout(set1_up_dout),
		.up_doutok(set1_up_doutok),
		.dn_rd(set1_dn_rd),
		.dn_wr(set1_dn_wr),
		.dn_mask(set1_dn_mask),
		.dn_addr(set1_dn_addr),
		.dn_din(set1_dn_din),
		.dn_dout(set1_dn_dout),
		.dn_dinok(set1_dn_dinok)
		);

endmodule
